package com.lsp.helper;

import com.lsp.annotation.Aspect;
import com.lsp.annotation.Service;
import com.lsp.proxy.AspectProxy;
import com.lsp.proxy.Proxy;
import com.lsp.proxy.ProxyFactory;
import com.lsp.proxy.TransactionProxy;
import com.lsp.util.ClassUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

public final class AopHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger(AopHelper.class);
    static {
        try {
            Map<Class<?>, Set<Class<?>>> aspectMap = createAspectMap();
            Map<Class<?>, List<Proxy>> targetMap = createTargetMap(aspectMap);
            for(Map.Entry<Class<?>, List<Proxy>> targetEntry : targetMap.entrySet()){
                Class<?> targetClass = targetEntry.getKey();
                List<Proxy> proxyList = targetEntry.getValue();
                /**
                 * 重点代码  Object proxy = ProxyFactory.createProxy(targetClass, proxyList);
                 */
                Object proxy = ProxyFactory.createProxy(targetClass, proxyList);
                // 覆盖Bean容器里的目标类对应实例，下次从Bean容器获得的就是代理对象
                BeanHelper.setBean(targetClass, proxy);
            }
        }catch (Exception e){
            LOGGER.error("aop failure ", e);
        }
    }
    private static Map<Class<?>, Set<Class<?>>> createAspectMap()  {
        HashMap<Class<?>, Set<Class<?>>> aspectMap = new HashMap<>();
        //获取切面类
        addAspectProxy(aspectMap);
        //获取事务类
        addTransactionProxy(aspectMap);
        return aspectMap;
    }

    /**
     * 获取普通切面类
     * @param aspectMap
     */
    private static void addAspectProxy(HashMap<Class<?>, Set<Class<?>>> aspectMap) {
        Set<Class<?>> aspectClassSet = ClassHelper.getClassSetBySuper(AspectProxy.class);
        for(Class<?> aspectClass : aspectClassSet){
            if(aspectClass.isAnnotationPresent(Aspect.class)){
                Aspect aspect = aspectClass.getAnnotation(Aspect.class);
                Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
                aspectMap.put(aspectClass, targetClassSet);
            }
        }
    }

    /**
     * 获取事务切面类
     * @param aspectMap
     */
    private static void addTransactionProxy(Map<Class<?>, Set<Class<?>>> aspectMap){
        Set<Class<?>> serviceClassSet = ClassHelper.getClassSetByAnnotation(Service.class);
        aspectMap.put(TransactionProxy.class,serviceClassSet);
    }

    private static Set<Class<?>> createTargetClassSet(Aspect aspect) {
        HashSet<Class<?>> targetClass = new HashSet<>();
        String pkg = aspect.pkg();
        String cls = aspect.cls();
        if(!pkg.equals("") && !cls.equals("")){
            try {
                targetClass.add(Class.forName(pkg + "." + cls));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }else if(!pkg.equals("")){
            targetClass.addAll(ClassUtil.getClassSet(pkg));
        }
        return targetClass;
    }

    /***
     * 获取目标类
     * 将切面类-目标集合的映射关系 转化为 目标类-切面对象列表的映射关系
     * @param aspectMap
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private  static Map<Class<?>, List<Proxy>> createTargetMap(Map<Class<?>, Set<Class<?>>> aspectMap) throws IllegalAccessException, InstantiationException {
        HashMap<Class<?>, List<Proxy>> targetMap = new HashMap<>();
       for(Map.Entry<Class<?>, Set<Class<?>>> proxyEntry : aspectMap.entrySet()){
           Class<?> aspectClass = proxyEntry.getKey();
           Set<Class<?>> targetClassSet = proxyEntry.getValue();
           for(Class<?> targetClass : targetClassSet){
               Proxy aspect = (Proxy)aspectClass.newInstance();
               if(targetMap.containsKey(targetClass)){
                   targetMap.get(targetClass).add(aspect);
               }else{
                   ArrayList<Proxy> aspectList = new ArrayList<>();
                   aspectList.add(aspect);
                   targetMap.put(targetClass, aspectList);
               }
           }
       }
       return targetMap;
    }
}
